﻿using Framework.Domain.Model;
using Framework.Domain.Enum;
using Framework.Infrastructure;
using Framework.Infrastructure.Cache.Redis;
using Framework.Infrastructure.RabbitMQ;
using Framework.MVC.Infrastructure;
using Framework.Repository.Service;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace Framework.MVC.Controllers
{
    public class AccountController : BaseController
    {
        SYS_BackUserService BackUserService = new SYS_BackUserService();

        #region PC端登录/退出
        /// <summary>
        /// 登录（PC后台）
        /// </summary>
        /// <param name="name">账号</param>
        /// <param name="pwd">密码</param>
        /// <param name="key">验证码在redis里的键</param>
        /// <param name="verifyCode">验证码在redis里的值</param>
        /// <returns></returns>
        [HttpPost]
        public JsonResult Login(string name, string pwd, string key, string varcode)
        {
            //非空验证
            if (string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(pwd))
                return Json(new ReturnModel_Common { success = false, code = ReturnCode_System.后台账号和密码不能为空, msg = "USERNAME_OR_PWD_CANT_BE_EMPTY" });
            if (string.IsNullOrWhiteSpace(key) || string.IsNullOrWhiteSpace(varcode))
                return Json(new ReturnModel_Common { success = false, code = ReturnCode_System.后台验证码不能为空, msg = "VERIFY_CODE_CANT_BE_EMPTY" });

            //长度验证
            name = name.Trim(); pwd = pwd.Trim();
            if ((name.Length < 2 || name.Length > 64) || (pwd.Length < 6 || pwd.Length > 128))
                return Json(new ReturnModel_Common { success = false, code = ReturnCode_System.后台账号或密码长度非法, msg = "USERNAME_OR_PWD_LENGTH_ILLEGAL" });

            //验证登录是否受限
            if (!IfLoginLimited(name, RedisFolderEnum.BKLoginFail))
                return Json(new ReturnModel_Common { success = false, code = ReturnCode_System.后台账号登录失败次数过多, msg = "LOGIN_LIMITED" });

            //验证验证码
            string verifyCodeRedis = RedisHelper.StringGet(key, RedisFolderEnum.BKCode);
            RedisHelper.KeyDelete(key, RedisFolderEnum.BKCode);//正确与否都干掉，验证码只能用一次

            if (string.IsNullOrWhiteSpace(verifyCodeRedis) || verifyCodeRedis.ToLower() != varcode.ToLower())
                return Json(new ReturnModel_Common { success = false, code = ReturnCode_System.后台登录验证码错误或过期, msg = "VERIFY_CODE_ERROR_OR_EXPIRED" });

            //登录日志
            SYS_LoginLog loginLog = new SYS_LoginLog { UserName = name, LoginIP = Common.GetIp(), LoginType = LoginLogType.BK };

            //验证账号密码
            string md5Pwd = Common.ToMD5(pwd);
            var backUser = BackUserService.FindSingle(u => u.UserName == name && u.UserPwd == md5Pwd);
            if (backUser == null)
            {
                LoginFail(name, RedisFolderEnum.BKLoginFail);

                loginLog.UserPwd = pwd;
                loginLog.IfLoginSuccess = false;
                RabbitMQPublish(loginLog);
                return Json(new ReturnModel_Common { success = false, code = ReturnCode_System.后台账号或密码错误, msg = "USERNAME_OR_PWD_ERROR" });
            }
            else
            {
                LoginSuccess(name, RedisFolderEnum.BKLoginFail);

                if (backUser.Status == State.Disable)
                {
                    loginLog.UserPwd = pwd;
                    loginLog.IfLoginSuccess = false;
                    RabbitMQPublish(loginLog);
                    return Json(new ReturnModel_Common { success = false, code = ReturnCode_System.后台账号被禁用, msg = "ACCOUNT_FORBIDDEN" });
                }
                else
                {
                    loginLog.IfLoginSuccess = true;
                    RabbitMQPublish(loginLog);

                    var token = Common.JWTEncodeBKUserInfo(new BKTokenModel { Id = backUser.Id, Guid = backUser.Guid, UserName = backUser.UserName, UserRealName = backUser.UserRealName, IsSuperAdmin = backUser.IsSuperAdmin });
                    RedisHelper.StringSet(backUser.Id.ToString(), token, PublicConst.BKTokenExpireMins, RedisFolderEnum.BKToken);//登录成功后设置token过期时间，每次调用接口设置同样过期时间，见BKTokenValidateAttribute.cs文件（token有效期内，用户操作就自动延时，永远不会掉线）

                    return Json(new ReturnModel_Data { data = new { Id = backUser.Id, Guid = backUser.Guid, UserName = backUser.UserName, UserRealName = backUser.UserRealName, Token = token } });
                }
            }
        }

        /// <summary>
        /// RabbitMQ消息发布
        /// </summary>
        /// <param name="loginLog"></param>
        private void RabbitMQPublish(SYS_LoginLog model)
        {
            RabbitPublisher.Publish(new RabbitModel_Base { ModelType = RabbitModel_Enum.LoginLog, LoginLog = model });
        }

        /// <summary>
        /// 退出（PC后台）
        /// </summary>
        /// <param name="token"></param>
        /// <returns></returns>
        [HttpPost]
        public JsonResult LogOut(string token)
        {
            var model = GetBKTokenModelByToken(token);
            if (model != null && model.Id > 0)
                RedisHelper.KeyDelete(model.Id.ToString(), RedisFolderEnum.BKToken);
            return Json(new ReturnModel_Common { });
        }
        #endregion

        #region 手机端登录/退出
        /// <summary>
        /// 登录（手机端）
        /// </summary>
        /// <param name="name">账号</param>
        /// <param name="pwd">密码</param>
        /// <returns></returns>
        [HttpPost]
        public JsonResult LoginMob(string name, string pwd)
        {
            if (name == "admin" && pwd == "admin")
            {
                //这里需要模仿后台登录的逻辑：记录登录日志、限制登录错误次数等


                var token = Common.JWTEncodePhoneUserInfo(new PhoneTokenModel { Id = 1, Guid = Guid.Parse("d7a65a4c-ed54-49d0-9f12-e3fb9fd7fe78"), UserName = name });
                RedisHelper.StringSet("1", token, PublicConst.PhoneTokenExpireMins, RedisFolderEnum.PhoneToken);//登录成功后设置token过期时间，每次调用接口设置同样过期时间，见PhoneTokenValidateAttribute.cs文件（token有效期内，用户操作就自动延时，永远不会掉线）

                return Json(new ReturnModel_Data { data = new { UserName = name, Token = token } });
            }
            else
            {
                return Json(new ReturnModel_Common { success = false, code = ReturnCode_System.手机端账号或密码错误, msg = "NAME_OR_PWD_ERROR" });
            }
        }

        /// <summary>
        /// 退出（手机端）
        /// </summary>
        /// <param name="token"></param>
        /// <returns></returns>
        [HttpPost]
        public JsonResult LogOutMob(string token)
        {
            var model = GetPhoneTokenModelByToken(token);
            if (model != null && model.Id > 0)
                RedisHelper.KeyDelete(model.Id.ToString(), RedisFolderEnum.PhoneToken);
            return Json(new ReturnModel_Common { });
        }
        #endregion

        #region 指定时间登录失败次数达到上限就不再允许登录
        /// <summary>
        /// 登录失败次数是否超过上限
        /// </summary>
        /// <param name="name">账号</param>
        /// <param name="folder">前台或后台，可选：RedisFolderEnum.BKLoginFail、RedisFolderEnum.PhoneLoginFail</param>
        /// <returns></returns>
        private bool IfLoginLimited(string name, RedisFolderEnum folder)
        {
            int count;
            if (folder == RedisFolderEnum.BKLoginFail)
                count = PublicConst.BKLoginErrorIntervalCount;
            else if (folder == RedisFolderEnum.PhoneLoginFail)
                count = PublicConst.PhoneLoginErrorIntervalCount;
            else
                return false;

            var loginFailTimes = RedisHelper.ListLength(name, folder);
            if (loginFailTimes > count - 1)
            {
                return false;
            }
            return true;
        }

        /// <summary>
        /// 登录成功，干掉Redis里的记录
        /// </summary>
        /// <param name="name">账号</param>
        /// <param name="folder">前台或后台，可选：RedisFolderEnum.BKLoginFail、RedisFolderEnum.PhoneLoginFail</param>
        private void LoginSuccess(string name, RedisFolderEnum folder)
        {
            RedisHelper.KeyDelete(name, folder);
        }

        /// <summary>
        /// 登录失败，写Redis记录
        /// </summary>
        /// <param name="name">账号</param>
        /// <param name="folder">前台或后台，可选：RedisFolderEnum.BKLoginFail、RedisFolderEnum.PhoneLoginFail</param>
        private void LoginFail(string name, RedisFolderEnum folder)
        {
            int time, count;
            if (folder == RedisFolderEnum.BKLoginFail)
            {
                time = PublicConst.BKLoginErrorIntervalTime;
                count = PublicConst.BKLoginErrorIntervalCount;
            }
            else if (folder == RedisFolderEnum.PhoneLoginFail)
            {
                time = PublicConst.PhoneLoginErrorIntervalTime;
                count = PublicConst.PhoneLoginErrorIntervalCount;
            }
            else
            {
                time = 0; count = 0;
            }

            var ifExist = RedisHelper.KeyExists(name, folder);
            RedisHelper.ListRightPush(name, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"), folder);
            if (!ifExist)
            {
                RedisHelper.KeyExpire(name, time, folder);
            }
            else
            {
                var loginFailTimes = RedisHelper.ListLength(name, folder);
                if (loginFailTimes == count)
                {
                    RedisHelper.KeyExpire(name, time, folder);//指定时间内登录失败次数达到指定次数，把这条记录过期时间设置为指定时间，指定时间内不再允许登录
                }
            }
        }
        #endregion

        #region Redis实现验证码
        /// <summary>
        /// 获取验证码
        /// </summary>
        /// <param name="key">键</param>
        /// <param name="fdType">前台或后台，0：后台、1：前台</param>
        /// <param name="codeType">验证码类型，1：4位数字验证码、2：4位数字字母验证码、3：6位数字验证码、4：6位数字字母验证码</param>
        /// <returns></returns>
        [HttpGet]
        public ActionResult GetVerifyCode(string key, int fdType = 0, int codeType = 3)
        {
            if (string.IsNullOrEmpty(key))
                return Json(new ReturnModel_Common { success = false, code = ReturnCode_System.操作失败, msg = "ILLEGAL_KEY" });

            RedisFolderEnum folder;
            if (fdType == 0)
                folder = RedisFolderEnum.BKCode;
            else if (fdType == 1)
                folder = RedisFolderEnum.PhoneCode;
            else
                return Json(new ReturnModel_Common { success = false, code = ReturnCode_System.操作失败, msg = "ILLEGAL_FDTYPE" });

            string strResult = string.Empty;
            switch (codeType)
            {
                case 1:
                default:
                    strResult = new Random().Next(1000, 10000).ToString();
                    break;
                case 2:
                    strResult = Common.GenerateRandomNumber(4);
                    break;
                case 3:
                    strResult = new Random().Next(100000, 1000000).ToString();
                    break;
                case 4:
                    strResult = Common.GenerateRandomNumber(6);
                    break;
            }
            if (folder == RedisFolderEnum.BKCode)
                RedisHelper.StringSet(key, strResult, PublicConst.BKVerifyCodeExpireMins, folder);
            else
                RedisHelper.StringSet(key, strResult, PublicConst.PhoneVerifyCodeExpireMins, folder);
            var bytes = Common.CreateValidateGraphic(strResult);
            return File(bytes, @"image/gif");
        }
        #endregion

    }
}